////////////////////////////////////////////////////////////////////
// File:	MeshManager.cpp
// Purpose: Manages all meshes/skinned meshes/quads in game. By reusing
//			data that is already loaded in or loading in new data on the fly.
// Author:	Jay Baird
////////////////////////////////////////////////////////////////////

#include "MeshManager.h"
#include "AnimationManager.h"
#include <io.h>
#include <fstream>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <glee.h>
#include <gl.h>
#include <glu.h>
#include <glext.h>
#include "..\..\src\Util\Math.h"
#include "Sphere.h"

using namespace MeshComponent;

#ifndef PAM_HEADER
#define PAM_HEADER "PALPABLE_AFTERTASTE_MESH_FMT"
#endif

#ifndef PAM_HEADER_VER
#define PAM_HEADER_VER 1.0f
#endif

typedef std::vector<MeshManager::meshInfo>::iterator MeshIter;
typedef std::vector<MeshManager::meshInfo>::const_iterator MeshIterConst;

// Init static variables:
MeshManager MeshManager::m_instance;

int MeshManager::Load(const char* fileName, vec3 scale, TextureParam param)
{
	if(!fileName) return -1;

	// Get rid of negs...
	scale = vec3(abs(scale.X()), abs(scale.Y()), abs(scale.Z()));

	// Check if the file is already loaded into memory...
	MeshIterConst iter = m_meshes.begin();

	int currID = 0;
	while(iter != m_meshes.end())
	{
		// Don't instance skinned meshes
		if((*iter).pMesh->Type() != MESH_TYPE_SKINNED_MESH)
		{
			if((strcmp(fileName, (*iter).fileName) == 0)
				&& ((*iter).scale == scale))
			{
				return currID;
			}
		}

		currID++;
		iter++;
	}

	// Wait were going to hack you!!
	if(strcmp(fileName, "bullet") == 0)
		return CreateSphere(0.25f, 2, "bullet");
	else if(strcmp(fileName, "drawbridge") == 0)
		return CreateBlock(3.0f, 1.0f, 1.0f, "drawbridge");
	//

	if(_access(fileName, 0) == -1) return -1;

	meshInfo& newMesh = meshInfo();

	unsigned meshSize = (unsigned)m_meshes.size();
	for (unsigned i = 0; i < meshSize; i++)
	{
		// Then it's not used
		if(!m_meshes[i].pMesh)
		{
			newMesh = m_meshes[i];
			break;
		}
	}

	// Setup scale
	newMesh.scale = scale;

	strcpy_s(newMesh.fileName, strlen(fileName)+1, fileName);

	// Read: header information //
	std::ifstream fin(fileName, std::ios_base::in | std::ios_base::binary);

	char buffer[32];
	float version = 0.0f;

	int size = sizeof(PAM_HEADER) + 1;
	fin.read(buffer, size);
	if(strcmp(buffer, PAM_HEADER) != 0) return -1;
	fin.read((char*)&version, sizeof(version));
	if(version != PAM_HEADER_VER) return -1;

	// Read: all the information for a mesh
	unsigned numBones, numTextures, numTriangles, numVertices;

	// Counts //
	fin.read((char*)&numTextures, sizeof(numTextures));
	fin.read((char*)&numTriangles, sizeof(numTriangles));
	fin.read((char*)&numVertices, sizeof(numVertices));
	fin.read((char*)&numBones, sizeof(numBones));

	// Skinned or not Skinned?
	
	if(numBones > 0)
		newMesh.pMesh = new SkinnedMesh;
	else newMesh.pMesh = new Mesh;

	Mesh* pMesh = newMesh.pMesh;

	pMesh->m_numTriangles = numTriangles;
	pMesh->m_numVertices = numVertices;

	// Data //
	if(pMesh->m_numTriangles == 0 || pMesh->m_numVertices == 0) return -1;

	//* Special case for textures
	if(numTextures != 0)
	{
		unsigned nameLength = 0;
		char buffer[MAX_PATH];

		fin.read((char*)&nameLength, sizeof(nameLength));
		fin.read((char*)&buffer, nameLength);

		std::string fileName = "Assets/Objects/";
		fileName += buffer;

		pMesh->m_textureID = TextureManager::GetInstance()->LoadTexture((char*)fileName.c_str(), param);
	}

	pMesh->m_triangles = new Triangle[pMesh->m_numTriangles];
	pMesh->m_vertices = new vec3[pMesh->m_numVertices];
	pMesh->m_normals = new vec3[pMesh->m_numVertices];
	pMesh->m_texCoords = new vec2[pMesh->m_numVertices];

	fin.read((char*)pMesh->m_triangles, sizeof(pMesh->m_triangles[0]) * pMesh->m_numTriangles);
	fin.read((char*)pMesh->m_vertices, sizeof(pMesh->m_vertices[0]) * pMesh->m_numVertices);
	fin.read((char*)pMesh->m_normals, sizeof(pMesh->m_normals[0]) * pMesh->m_numVertices);
	fin.read((char*)pMesh->m_texCoords, sizeof(pMesh->m_texCoords[0]) * pMesh->m_numVertices);

	if(strcmp(fileName, "Assets/Objects/PA_Block.pam") == 0
	|| strcmp(fileName, "Assets/Objects/PA_DialogBlock.pam") == 0)
	{
		for (unsigned i = 0; i < pMesh->m_numVertices; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				if(pMesh->m_vertices[i][j] > 0.0f)
					pMesh->m_vertices[i][j] = scale[j];
				else pMesh->m_vertices[i][j] = 0.0f;

				if(pMesh->m_normals[i][j] > 0.0f)
					pMesh->m_normals[i][j] = scale[j];
				else pMesh->m_normals[i][j] = 0.0f;
			}
		}
	}
	else
	{
		// Scale if greater or less than 1.0f
		if(scale.X() != 1.0f || scale.Y() != 1.0f || scale.Z() != 1.0f)
		{
			for (unsigned i = 0; i < pMesh->m_numVertices; i++)
			{
				pMesh->m_vertices[i].X() *= scale.X();
				pMesh->m_vertices[i].Y() *= scale.Y();
				pMesh->m_vertices[i].Z() *= scale.Z();

				pMesh->m_normals[i].X() *= scale.X();
				pMesh->m_normals[i].Y() *= scale.Y();
				pMesh->m_normals[i].Z() *= scale.Z();
			}
		}
	}

	if(numBones > 0)
	{
		SkinnedMesh* pSkinned = (SkinnedMesh*)pMesh;
		pSkinned->m_numBones = numBones;
		LoadSkinned(pSkinned, fin);
	}

	// Done reading
	fin.close();

	BuildVBOS(pMesh);

	// Create a bounding volume around the newMesh data
	newMesh.boundingVol.FindDimensions(pMesh->m_vertices, pMesh->m_numVertices);

	m_meshes.push_back(newMesh);

	return (int)(m_meshes.size() - 1);
}

void MeshManager::LoadSkinned(SkinnedMesh* pMesh, std::ifstream& fin)
{
	pMesh->m_bones = new Bone[pMesh->m_numBones];
	pMesh->m_influences.resize(pMesh->m_numVertices);

	// Read in bones...
	unsigned strLength = 0;
	char buffer[MAX_PATH];
	for (unsigned i = 0; i < pMesh->m_numBones; i++)
	{
		// Read in parent index
		fin.read((char*)&pMesh->m_bones[i].parent, sizeof(pMesh->m_bones[i].parent));

		// Read in children count
		fin.read((char*)&pMesh->m_bones[i].childCount, sizeof(pMesh->m_bones[i].childCount));

		if(pMesh->m_bones[i].childCount != 0)
			pMesh->m_bones[i].children = new unsigned[pMesh->m_bones[i].childCount];

		// Read in all the children indices
		for (unsigned j = 0; j < pMesh->m_bones[i].childCount; j++)
			fin.read((char*)&pMesh->m_bones[i].children[j], sizeof(pMesh->m_bones[i].children[j]));

		// Read in bone name
		fin.read((char*)&strLength, sizeof(strLength));
		fin.read(buffer, strLength);
		pMesh->m_bones[i].name = buffer;
	}

	// Read in influences per vertex...
	unsigned numWeights = 0;
	for (unsigned i = 0; i < pMesh->m_numVertices; i++)
	{
		// Read in weight count per vertex
		fin.read((char*)&numWeights, sizeof(numWeights));
		pMesh->m_influences[i].resize(numWeights);

		// Read in influence data
		for (unsigned j = 0; j < numWeights; j++)
		{
			fin.read((char*)&pMesh->m_influences[i][j].boneIndex, sizeof(pMesh->m_influences[i][j].boneIndex));
			fin.read((char*)&pMesh->m_influences[i][j].weight, sizeof(pMesh->m_influences[i][j].weight));
		}
	}
}

int MeshManager::LoadQuad(const char* textureFile, TextureParam param, unsigned texTilesX, unsigned texTilesY)
{
	// Clamp to 1
	if(texTilesX == 0) texTilesX = 1;
	if(texTilesY == 0) texTilesY = 1;

	int textureID = TextureManager::GetInstance()->LoadTexture(textureFile, param);

	// Check if THIS quad is already loaded into memory...
	MeshIterConst iter = m_meshes.begin();

	int currID = 0;
	int firstQuadID = -1;
	while(iter != m_meshes.end())
	{
		if(strcmp("_QUAD", (*iter).fileName) == 0)
		{
			vec2 meshTexTiles = (*iter).pMesh->GetTextureTileCount();

			if(meshTexTiles == 1)
				firstQuadID = currID;	// For reusabiliy purposes for a quad's VBO

			if((*iter).pMesh->GetTextureID() == textureID
			&& meshTexTiles.X() == texTilesX
			&& meshTexTiles.Y() == texTilesY)
				return currID;
		}

		currID++;
		iter++;
	}

	// We have not created a quad yet so let's create one for the first time using two triangles!
	meshInfo quad;
	strcpy_s(quad.fileName, _countof(quad.fileName), "_QUAD");
	Mesh* mesh = new Mesh;
	quad.pMesh = mesh;
	mesh->m_numTriangles = 2;
	mesh->m_numVertices = 4;

	// Fake allocating memory for a arbritray quad
	if(firstQuadID == -1 || texTilesX > 1 || texTilesY > 1)
	{
		mesh->m_triangles = (Triangle*)QuadData::QuadTriangles;
		mesh->m_vertices = (vec3*)QuadData::QuadVertices;

		if(texTilesX > 1 || texTilesY > 1)
		{
			mesh->m_texCoords = new vec2[4];
			memcpy(mesh->m_texCoords, QuadData::QuadTexCoords, sizeof(vec2) * 4);	

			// If the texture is tiling then scale the tex coords
			mesh->m_texTiles = vec2(texTilesX, texTilesY);

			for (unsigned i = 0; i < 4; i++)
			{
				mesh->m_texCoords[i].X() *= texTilesX;
				mesh->m_texCoords[i].Y() *= texTilesY;
			}
		}
		else mesh->m_texCoords = (vec2*)QuadData::QuadTexCoords;

		BuildVBOS(mesh);

		// Set back to null now that things are setup
		mesh->m_triangles = NULL;
		mesh->m_vertices = NULL;

		// not allocated Memory, set to null
		if(texTilesX == 1 && texTilesY == 1)
			mesh->m_texCoords = NULL;
	}
	else mesh->m_buffer = m_meshes[firstQuadID].pMesh->m_buffer;	// Copy over VBO information to the new quad

	// Setup the textureID
	mesh->m_textureID = textureID;

	m_meshes.push_back(quad);

	return (int)(m_meshes.size() - 1);
}

bool MeshManager::Remove(int id)
{
	if(IsAvailable(id))
	{
		m_meshes[id].pMesh->Delete();
		m_meshes[id].fileName[0] = '\0';
		m_meshes[id].scale = vec3(1.0f);
		delete m_meshes[id].pMesh;

		return true;
	}
	
	return false;
}

void MeshManager::RemoveAll()
{
	unsigned meshCount = (unsigned)m_meshes.size();
	for (unsigned i = 0; i < meshCount; i++)
		Remove(i);

	m_meshes.clear();
}

int MeshManager::CreateSphere(float radius, unsigned divisions, const char* name)
{
	// Get rid of any negs...
	radius = abs(radius);

	Sphere sphereData;
	sphereData.CreateSphere(divisions, radius);

	meshInfo sphere;
	sphere.pMesh = new Mesh;

	if(name != NULL)
		strcpy_s(sphere.fileName, strlen(name)+1, name); 

	Mesh* mesh = sphere.pMesh;
	
	// Copy over the sphere data
	const std::vector<Sphere::VertChunk>* pNormsVerts = sphereData.GetNormalsAndVertices();
	mesh->m_numVertices = (unsigned)pNormsVerts->size();
	mesh->m_vertices = new vec3[mesh->m_numVertices];
	mesh->m_normals = new vec3[mesh->m_numVertices];
	for (unsigned i = 0; i < mesh->m_numVertices; i++)
	{
		mesh->m_vertices[i] = vec3((*pNormsVerts)[i].vertex);
		mesh->m_normals[i] = vec3((*pNormsVerts)[i].normal);
	}

	const std::vector<Triangle>* pTriangles = sphereData.GetTriangles();
	mesh->m_numTriangles = (unsigned)pTriangles->size();
	mesh->m_triangles = new Triangle[mesh->m_numTriangles];
	for (unsigned i = 0; i < mesh->m_numTriangles; i++)
	{
		mesh->m_triangles[i].index[0] = (*pTriangles)[i].index[0];
		mesh->m_triangles[i].index[1] = (*pTriangles)[i].index[1];
		mesh->m_triangles[i].index[2] = (*pTriangles)[i].index[2];
	}

	BuildVBOS(mesh);

	m_meshes.push_back(sphere);

	return (int)(m_meshes.size() - 1);
}

int MeshManager::CreateBlock(float width, float height, float depth, const char* name)
{
	// Get rid of any negs...
	width = abs(width);
	height = abs(height);
	depth = abs(depth);

	float verts[] =
	{
		-width, -height, depth,   // vertex v0
		width,  -height, depth,   // vertex v1
		width,  -height, -depth,  // vertex v2
		-width, -height, -depth,  // vertex v3
		-width, height,  depth,   // vertex v4
		width,  height,  depth,   // vertex v5
		width,  height,  -depth,  // vertex v6 
		-width, height,  -depth   // vertex v7
	};

	unsigned short indices[] =
	{
		0, 1, 4,  // polygon v0,v1,v4
		1, 5, 4,  // polygon v1,v5,v4
		1, 2, 5,  // polygon v1,v2,v5
		2, 6, 5,  // polygon v2,v6,v5
		2, 3, 6,  // polygon v2,v3,v6
		3, 7, 6,  // polygon v3,v7,v6
		3, 0, 7,  // polygon v3,v0,v7
		0, 4, 7,  // polygon v0,v4,v7
		4, 5, 7,  // polygon v4,v5,v7
		5, 6, 7,  // polygon v5,v6,v7
		3, 2, 0,  // polygon v3,v2,v0
		2, 1, 0,  // polygon v2,v1,v0
	};

	meshInfo block;
	block.pMesh = new Mesh;
	if(name != NULL)
		strcpy_s(block.fileName, strlen(name)+1, name); 
	Mesh* mesh = block.pMesh;

	mesh->m_numVertices = 8;
	mesh->m_numTriangles = 12;
	mesh->m_vertices = new vec3[mesh->m_numVertices];
	mesh->m_triangles = new MeshComponent::Triangle[mesh->m_numTriangles];
	
	// Copy over the indices
	// Vertices
	int curr = 0;
	for (int i = 0; i < 24; i += 3)
	{
		mesh->m_vertices[curr].X() = verts[i];
		mesh->m_vertices[curr].Y() = verts[i + 1];
		mesh->m_vertices[curr].Z() = verts[i + 2];

		curr++;
	}

	// Indices
	curr = 0;
	for (int i = 0; i < 36; i += 3)
	{
		mesh->m_triangles[curr].index[0] = indices[i];
		mesh->m_triangles[curr].index[1] = indices[i + 1];
		mesh->m_triangles[curr].index[2] = indices[i + 2];

		curr++;
	}

	// Build the Vertex Buffer Objects for the mesh...
	BuildVBOS(mesh);

	// Add the block to the mesh list
	m_meshes.push_back(block);

	return (int)(m_meshes.size() - 1);
}

void MeshManager::BuildVBOS(Mesh* pMesh)
{
	unsigned* buffers = pMesh->m_buffer.buffers;

	// Now bind the buffers for pair-to-pair for each pointer
	if(pMesh->m_vertices)
	{
		if(pMesh->Type() != MESH_TYPE_SKINNED_MESH)
		{
			glGenBuffers(1, &buffers[MESH_BUFFER_TYPE_VERTEX]);
			glBindBuffer(GL_ARRAY_BUFFER, buffers[MESH_BUFFER_TYPE_VERTEX]);
			glBufferData(GL_ARRAY_BUFFER, pMesh->m_numVertices * sizeof(vec3), pMesh->m_vertices, GL_STATIC_DRAW);
			glVertexPointer(3, GL_FLOAT, 0, 0);
		}
		else	// Skinned Mesh, so create a streaming VBO
		{
			SkinnedMesh* pSkinned = (SkinnedMesh*)pMesh;

			glGenBuffers(1, &buffers[MESH_BUFFER_TYPE_VERTEX]);
			glBindBuffer(GL_ARRAY_BUFFER, buffers[MESH_BUFFER_TYPE_VERTEX]);
			glBufferData(GL_ARRAY_BUFFER, pSkinned->m_numVertices * sizeof(vec3), pSkinned->m_vertices, GL_DYNAMIC_DRAW);
			glVertexPointer(3, GL_FLOAT, 0, 0);
		}
	}

	if(pMesh->m_normals)
	{
		glGenBuffers(1, &buffers[MESH_BUFFER_TYPE_NORMAL]);
		glBindBuffer(GL_ARRAY_BUFFER, buffers[MESH_BUFFER_TYPE_NORMAL]);
		glBufferData(GL_ARRAY_BUFFER, pMesh->m_numVertices * sizeof(vec3), pMesh->m_normals, GL_STATIC_DRAW);
		glNormalPointer(GL_FLOAT, 0, 0);
	}

	if(pMesh->m_texCoords)
	{
		glGenBuffers(1, &buffers[MESH_BUFFER_TYPE_TEXCOORD]);
		glBindBuffer(GL_ARRAY_BUFFER, buffers[MESH_BUFFER_TYPE_TEXCOORD]);
		glBufferData(GL_ARRAY_BUFFER, pMesh->m_numVertices * sizeof(vec2), pMesh->m_texCoords, GL_STATIC_DRAW);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}

	if(pMesh->m_triangles)
	{
		glGenBuffers(1, &buffers[MESH_BUFFER_TYPE_INDEX]);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[MESH_BUFFER_TYPE_INDEX]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, pMesh->m_numTriangles * sizeof(Triangle), pMesh->m_triangles, GL_STATIC_DRAW);
		glIndexPointer(GL_UNSIGNED_SHORT, 0, 0); 
	}

	// Unbind the buffer for "safe" reasons
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}